﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Gakita.Model
{
    /* This class handles most of the game logic */
    class Model
    {
        private const int _playerRespawnTime                =       2001;

        private int _currenttimeInMs                        =       1;
        private bool _isGamePausedAfterDeath                =       false;

        private Level _level                                =       new Level();
        private Player _player                              =       new Player();

        public static bool _isSelectLevelModeStarted        =       false;
        public static bool _gameIsRunning                   =       false;
        public static bool _isSelectLevelMode               =       false;
        public static Level.Levels _selectedLevel;

        public bool _isStandingOnFlyingPlatform             =       false;
        public bool _hasCollidedWithGround;


        public void Update(float aElapsedTime, View.Menu.IModelObserver[] aObservers, View.RabbitPlayerDeathListener aListener)
        {
            // If we're in Level selection mode, we set the correct level and starting position;
            if (_isSelectLevelMode == true && _isSelectLevelModeStarted == false)
            {
                if (_selectedLevel == Level.Levels.fourth)
                {
                    _player.ChangePlayerStartingPositionToLevelFour();
                }
                else
                {
                    _player.SetPlayerStartingPositionToStandard();
                }

                _player.ResetPlayerPosition();
                ResetGame();
                _level.SetCurrentLevel(_selectedLevel);

                _isSelectLevelModeStarted = true;
            }

            if (_gameIsRunning == false)
            {
                _gameIsRunning = true;
            }

            // The time that has elapsed since the last update is converted to milliseconds and then added to the current time.
            _currenttimeInMs += (int)(aElapsedTime * 1000);
            
            int livesLeft = _player.GetLivesLeft();

            // If the player have no lives left, we end the game.
            if (livesLeft == 0)
            {
                _player.SetPlayerStartingPositionToStandard();
                ResetGame();
                _isSelectLevelMode = false;
                _gameIsRunning = false;
                return;
            }

            // When the player dies, the game gets paused. Once the pause is over, the player dies and loses a life. 
            // When the game gets paused the current time is set to a negative amount equal to the time the game is supposed to be paused.
            // Once that time reaches a positive value again, we kill the player and the game is no longer paused.
            if (_currenttimeInMs > 0 && _isGamePausedAfterDeath == true)
            {
                PlayerDied();
                _isGamePausedAfterDeath = false;
            }
            // If the game isn't paused, the game logic is run.
            else if(_isGamePausedAfterDeath == false)
            {
                foreach (Enemies.Rabbit enemy in _level._rabbits)
                {
                    enemy.Update(aElapsedTime, _level);
                }
                foreach (Enemies.SpikyThing enemy in _level._spikyThings)
                {
                    enemy.Update(aElapsedTime);
                }
                foreach (Enemies.FlyingEnemy enemy in _level._flyingEnemies)
                {
                    enemy.Update(aElapsedTime, _level);
                }
                foreach (Miscellaneous.FlyingPlatform flyingPlatform in _level._flyingPlatforms)
                {
                    flyingPlatform.Update(aElapsedTime);
                }
                foreach (Enemies.GiantRabbit gRabbit in _level._giantRabbits)
                {
                    gRabbit.Update(aElapsedTime, _level);
                }

                // The new and old position of the player are saved in variable to use for checking collisions
                Vector2 oldPosition = _player.GetPosition();
                _player.Update(aElapsedTime);
                Vector2 newPosition = _player.GetPosition();

                // Since the world is 20 tiles wide, the player is outside of the world if he reaches 21. 
                // When that happens, the player dies. 
                if (newPosition.Y > 21)
                {
                    PauseTheGameWhenPlayerIsAboutToDie(aListener);
                    newPosition = _player.GetPosition();
                }

                // We create a few new variables to store information regarding the players collision 
                // with the ground or a flying platform.
                Miscellaneous.FlyingPlatform platform = null;
                _isStandingOnFlyingPlatform = false;
                _hasCollidedWithGround = false;

                Vector2 speed = _player.GetSpeed();

                // We now checks for collisions with tiles and platforms
                Vector2 afterCollidedPos = Collide(oldPosition, newPosition, _player._size, ref speed, out _hasCollidedWithGround, aElapsedTime,                                                        out _isStandingOnFlyingPlatform, ref platform);

                // The player is standing on a platform, so we call the setPlatform function to assign the player to said platform
                if (platform != null)
                {
                    _player.SetPlatform(platform);
                }

                // A new speed and position is assigned depending on the results of the collide function
                _player.SetPosition(afterCollidedPos.X, afterCollidedPos.Y);
                _player.SetSpeed(speed.X, speed.Y);

                // We then check collision for the various enemies and kills the player when appropriate.
                foreach (Enemies.SpikyThing spikyThing in _level._spikyThings)
                {
                    if (_level.IsPlayerCollidingWithASpikyThing(_player.GetPosition(), _player._size,
                                                                spikyThing.GetPosition(), spikyThing._size))
                    {
                        PauseTheGameWhenPlayerIsAboutToDie(aListener);
                    }
                }
                foreach (Enemies.GiantRabbit giantRabbit in _level._giantRabbits)
                {
                    if(_level.IsPlayerCollidingWithAGiantRabbit(_player.GetPosition(), _player._size,
                                                                giantRabbit.GetPosition(), giantRabbit._size))
                    {
                        PauseTheGameWhenPlayerIsAboutToDie(aListener);
                    }
                }
                foreach (Enemies.Rabbit rabbit in _level._rabbits)
                {
                    bool isTheRabbitSupposedToDieNow = false;

                    // If any rabbits are dead, we don't run collision checks on them
                    if (rabbit.IsAlive() == true)
                    {
                        if (_level.IsPlayerCollidingWithARabbit(_player.GetPosition(), _player._size,
                                                                rabbit.GetPosition(), rabbit._size,
                                                                out isTheRabbitSupposedToDieNow))
                        {
                            //The isTheRabbitSupposedToDieNow will tell us if the rabbit or the player is supposed to die.
                            if (isTheRabbitSupposedToDieNow == true)
                            {
                                rabbit.Die();
                                aListener.RabbitIsDead(rabbit);
                                // When the player kills a rabbit, he bounces slightly.
                                _player.Bounce();
                            }
                            else
                            {
                                PauseTheGameWhenPlayerIsAboutToDie(aListener);
                            }
                        }
                    }

                }

                // Once the player reaches the cake at the end of each level, we're supposed to get to the in between levels screen,
                // and later start the next level
                if (_level.isPlayerCollidingWithCake(_player.GetPosition(), _player._size))
                {
                    // We alert the observers that the level is over, to allow switching to the in between level screen and to start
                    // uploading the highscore
                    foreach (View.Menu.IModelObserver observer in aObservers)
                    {
                        observer.LevelIsOver(GetCurrentTime(), _level.GetCurrentLevel());
                    }

                    _player.SetPlayerStartingPositionToStandard();

                    // If the player is in level selection mode, the game is over after a level is complete, 
                    // so we reset the game and the player.
                    if (_isSelectLevelMode == true)
                    {
                        _player.ResetPlayerPosition();
                        ResetGame();
                        _gameIsRunning = false;
                    }
                    // If the player is not in level selection mode and completed the last level, we reset the game aswell.
                    else if (_level.GetCurrentLevel() == Level.Levels.fourth)
                    {
                        _player.ResetPlayerPosition();
                        ResetGame();
                    }
                    else
                    {
                        // The level is set to the next one and the player position and time is reset, in preperation for when 
                        // the next level starts
                        _level.SetCurrentLevel(_level.GetCurrentLevel() + 1);

                        // Level four got a different starting position for the player
                        if (_level.GetCurrentLevel() == Level.Levels.fourth)
                        {
                            _player.ChangePlayerStartingPositionToLevelFour();
                        }
                        _player.ReturnPlayerToStartPosition();
                        _currenttimeInMs = 1;
                    }

                }

                // If the player hits the ground, he is not jumping and the variable keeping track of the players jumps is reset
                if (_hasCollidedWithGround == true)
                {
                    _player.ResetTimesJumped();
                }
            }
        }

        public void MoveLeft()
        {
            _player.MoveLeft();
        }

        public void MoveRight()
        {
            _player.MoveRight();
        }

        /* This method makes the player jump, assuming he hasn't already jumped two times */
        public void DoJump()
        {
            if (_player.GetNumberOfJumps() < 2)
            {
                _player.DoJump();
            }
        }

        /* This method is called when the game is over. It resets the game to the start of the first level in case 
           the user wants to play again */
        public void ResetGame()
        {
            _player.ResetPlayerPosition();
            _player.SetLivesLeft(3);
            _level.SetCurrentLevel(Level.Levels.first);
            _isGamePausedAfterDeath = false;
            _currenttimeInMs = 1;
        }

        /* This method tells the player object to kill itself and 
           respawns all the rabbits in the current level */
        public void PlayerDied()
        {
            _player.Die();
            _level.RespawnRabbits();
        }

        /* This method pauses the game. It's called when the player collides with an enemy or falls off the screen */
        public void PauseTheGameWhenPlayerIsAboutToDie(View.RabbitPlayerDeathListener aListener)
        {
            aListener.PlayerIsDead();
            _isGamePausedAfterDeath = true;

            // The current time is set to a negative amount equal to the lenght of the pause
            _currenttimeInMs = -_playerRespawnTime;
        }


        /* This method deals with the player colliding with tiles or flying platforms. It will return a new position for the player,
           aswell as information regarding what flying platform the player collided with, if applicatable */
        private Vector2 Collide(Vector2 aOldPosition, Vector2 aNewPosition, Vector2 aSize, ref Vector2 aVelocity, out bool aOutCollideGround,                                float aElapsedTime, out bool aIsStandingOnFlyingPlatform, ref Miscellaneous.FlyingPlatform aPlatform)
        {
            Miscellaneous.FlyingPlatform platform = null;
            bool isAPlatform = false;
            aIsStandingOnFlyingPlatform = false;
            aOutCollideGround = false;

            // Can we move to the position safely?
            if (_level.IsCollidingAt(aNewPosition, aSize, ref isAPlatform, ref platform))
            {
                aIsStandingOnFlyingPlatform = isAPlatform;
                aPlatform = platform;

                // if not try only the X movement, indicates that a collision with ground or roof has occured
                Vector2 xMove = new Vector2(aNewPosition.X, aOldPosition.Y);
                if (aVelocity.Y > 0 && aOldPosition.Y - (int)aOldPosition.Y > 0.9f)
                {
                    xMove.Y = (int)aOldPosition.Y + 0.99f;
                }

                if (_level.IsCollidingAt(xMove, aSize, ref isAPlatform, ref platform) == false)
                {
                    //did we collide with ground?
                    if (aVelocity.Y > 0)
                    {
                        aOutCollideGround = true;
                        aVelocity.Y = 0; //no bounce
                    }
                    else
                    {
                        //collide with roof
                        aVelocity.Y *= -1.0f; //reverse the y velocity and some speed lost in the collision
                    }

                    return xMove;
                }
                else
                {
                    // try Y movement, indicates that a collision with wall has occured
                    Vector2 yMove = new Vector2(aOldPosition.X, aNewPosition.Y);
                    if (_level.IsCollidingAt(yMove, aSize, ref isAPlatform, ref platform) == false)
                    {
                        aVelocity.X *= 0.5f;
                        
                        // To stop the player from getting stuck in flying platforms, the player will get the platforms speed added to
                        // his position, so that the platform pushes the player away.
                        if (aPlatform != null)
                        {
                            yMove = new Vector2 (yMove.X + aPlatform.getPlatformSpeed().X * aElapsedTime, yMove.Y);
                            aVelocity.X = 0f;
                        }

                        return yMove;
                    }

                    if (aVelocity.Y > 0)
                    {
                        aOutCollideGround = true;
                    }
                    aVelocity.X = 0; //no bounce
                    aVelocity.Y = 0; //no bounce
                }

                // To stop the player from getting stuck in flying platforms, the player will get the platforms speed added to
                // his position, so that the platform pushes the player away.
                if (aPlatform != null)
                {
                    aOldPosition = new Vector2(aOldPosition.X + aPlatform.getPlatformSpeed().X * aElapsedTime, aOldPosition.Y);
                }
                // Remain at the same position
                return aOldPosition;
            }
            aIsStandingOnFlyingPlatform = isAPlatform;
            return aNewPosition;
        }

        public int GetCurrentTime()
        {
            return _currenttimeInMs;
        }

        public bool isGamePausedAfterDeath()
        {
            return _isGamePausedAfterDeath;
        }

        public int GetPlayerLivesLeft()
        {
            return _player.GetLivesLeft();
        }

        public Vector2 GetPlayerPosition()
        {
            return _player.GetPosition();
        }

        public Vector2 GetPlayerSpeed()
        {
            return _player.GetSpeed();
        }

        public Level GetLevel()
        {
            return _level;
        }

        public bool IsTheGameStillRunning()
        {
            return Model._gameIsRunning;
        }
    }
}
